import os
import re
import html
import json
import requests
import subprocess
from lxml import etree
from bs4 import BeautifulSoup
import urllib3
urllib3.disable_warnings()


class Video(object):
    def get_title(self, url):
        req = requests.get(url)
        bs = BeautifulSoup(req.text, "lxml")
        title_lable = bs.find_all("h1", class_="video-title tit")
        title_source = re.findall("\">(.*?)</h1>", str(title_lable))
        title = html.unescape(str(title_source)[2:-2])

        return title

    def get_video(self, url, dir_path, video_name, audio_name):
        req = requests.get(url)
        element = etree.HTML(req.content)
        video_info = element.xpath('/html/head/script[4]/text()')[0].strip()
        video_json = json.loads(video_info[20:])

        url_video = video_json["data"]["dash"]["video"][0]["baseUrl"]
        url_audio = video_json["data"]["dash"]["audio"][0]["baseUrl"]

        if not os.path.exists(dir_path):
            os.mkdir(dir_path)

        self.download_video(url_index=url, url_resource=url_video, file_path=dir_path + video_name)
        self.download_video(url_index=url, url_resource=url_audio, file_path=dir_path + audio_name)

    def download_video(self, url_index, url_resource, file_path):
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/80.0.3970.5 Safari/537.36',
            'Referer': 'https://www.bilibili.com/'
        }
        headers.update({'Referer': url_index})
        session = requests.session()

        session.options(url=url_resource, headers=headers, verify=False)

        begin = 0
        end = 1024 * 512 - 1
        flag = 0
        while True:
            # 添加请求头键值对,写上 range:请求字节范围
            headers.update({'Range': 'bytes=' + str(begin) + '-' + str(end)})

            # 获取视频分片
            res = session.get(url=url_resource, headers=headers, verify=False)

            # HTTP416状态码代表的意思是:请求的范围无效
            if res.status_code != 416:
                # 响应码不为为416时有数据
                begin = end + 1
                end = end + 1024 * 512
            else:
                headers.update({'Range': str(end + 1) + '-'})
                res = session.get(url=url_resource, headers=headers, verify=False)
                flag = 1

            with open(file_path, 'ab') as f:
                f.write(res.content)
                f.flush()

            if flag == 1:
                f.close()
                break

    def combine_file(self, video_path, audio_path, output_path):
        subprocess.call(("C:/ffmpeg-2022-08-08-git-56973eb687-full_build/bin/ffmpeg -i " + video_path + " -i " + audio_path + " -c copy " + output_path), shell=True)

        os.remove(video_path)
        os.remove(audio_path)


if __name__ == "__main__":
    url_index = input("请在下方输入bilibili视频url\nurl = ")
    dir_path = input("请在下方输入文件保存地址(绝对路径)\ndir_path = ")
    if "\\" not in dir_path[-1]:
        dir_path = dir_path + "\\"

    bg = Video()
    video_name = "video.mp4"
    audio_name = "audio.mp3"
    file_name = bg.get_title(url_index)
    output_path = dir_path + file_name + ".mp4"

    print("请耐心等待......")
    bg.get_video(url_index, dir_path, video_name, audio_name)
    bg.combine_file(dir_path + video_name, dir_path + audio_name, output_path)
    print("下载完成！")

    os.system("pause")
